/*
 * copyright 2008 Edouard TISSERANT
 * copyright 2011 Mario de Sousa (msousa@fe.up.pt)
 *
 * Offered to the public under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
 * General Public License for more details.
 *
 * This code is made available on the understanding that it will not be
 * used in safety-critical situations without a full and competent review.
 */

/****
 * IEC 61131-3 standard function library
 */

/* NOTE: This file is full of (what may seem at first) very strange macros.
 *       If you want to know what all these strange macros are doing,
 *       just parse this file through a C preprocessor (e.g. cpp),
 *       and analyse the output!
 *       $gcc -E iec_std_lib.h
 */

#ifndef IEC_STD_LIB_H
#define IEC_STD_LIB_H


#include <stdarg.h>

#ifdef DEBUG_IEC
#define DBG(...) printf(__VA_ARGS__);
#define DBG_TYPE(TYPENAME, name) __print_##TYPENAME(name);
#else
#define DBG(...)
#define DBG_TYPE(TYPENAME, name)
#endif

#include "iec_types_all.h"

#define NULL 0


#define __lit(type, value, ...) (type)value##__VA_ARGS__
// Keep this macro expention step to let sfx(__VA_ARGS__) change into L or LL
#define __literal(type, value, ...) __lit(type,value,__VA_ARGS__)

#define __BOOL_LITERAL(value) __literal(BOOL,value)
#define __SINT_LITERAL(value) __literal(SINT,value)
#define __INT_LITERAL(value) __literal(INT,value)
#define __DINT_LITERAL(value) __literal(DINT,value,__32b_sufix)
#define __LINT_LITERAL(value) __literal(LINT,value,__64b_sufix)
#define __USINT_LITERAL(value) __literal(USINT,value)
#define __UINT_LITERAL(value) __literal(UINT,value)
#define __UDINT_LITERAL(value) __literal(UDINT,value,__32b_sufix)
#define __ULINT_LITERAL(value) __literal(ULINT,value,__64b_sufix)
#define __REAL_LITERAL(value) __literal(REAL,value)
#define __LREAL_LITERAL(value) __literal(LREAL,value,__64b_sufix)
#define __TIME_LITERAL(value) __literal(TIME,value)
#define __DATE_LITERAL(value) __literal(DATE,value)
#define __TOD_LITERAL(value) __literal(TOD,value)
#define __DT_LITERAL(value) __literal(DT,value)
#define __STRING_LITERAL(count, value) (STRING){count,value}
#define __BYTE_LITERAL(value) __literal(BYTE,value)
#define __WORD_LITERAL(value) __literal(WORD,value)
#define __DWORD_LITERAL(value) __literal(DWORD,value,__32b_sufix)
#define __LWORD_LITERAL(value) __literal(LWORD,value,__64b_sufix)


typedef union __IL_DEFVAR_T {
    BOOL BOOLvar;

    SINT SINTvar;
    INT INTvar;
    DINT DINTvar;
    LINT LINTvar;

    USINT USINTvar;
    UINT UINTvar;
    UDINT UDINTvar;
    ULINT ULINTvar;

    BYTE BYTEvar;
    WORD WORDvar;
    DWORD DWORDvar;
    LWORD LWORDvar;

    REAL REALvar;
    LREAL LREALvar;

    TIME TIMEvar;
    TOD TODvar;
    DT DTvar;
    DATE DATEvar;
} __IL_DEFVAR_T;
/****************************/
/* Notify IEC runtime error */
/****************************/

/* function that generates an IEC runtime error */
static inline void __iec_error(void);

/*******************/
/* Math Operations */
/*******************/
extern double pow(double x, double y);

static inline double __expt(double in1, double in2) {
    return pow(in1, in2);
}


/*******************************/
/* Time normalization function */
/*******************************/

static inline void __normalize_timespec(IEC_TIMESPEC *ts) {
    if (ts->tv_nsec < -1000000000 || ((ts->tv_sec > 0) && (ts->tv_nsec < 0))) {
        ts->tv_sec--;
        ts->tv_nsec += 1000000000;
    }
    if (ts->tv_nsec > 1000000000 || ((ts->tv_sec < 0) && (ts->tv_nsec > 0))) {
        ts->tv_sec++;
        ts->tv_nsec -= 1000000000;
    }
}

/**********************************************/
/* Time conversion to/from timespec functions */
/**********************************************/
/* NOTE: The following function was turned into a macro, so it could be used to initialize the initial value of TIME variables.
 *       Since each macro parameter is evaluated several times, the macro may result in multiple function invocations if an expression
 *       containing a function invocation is passed as a parameter. However, currently matiec only uses this conversion macro with
 *       constant literals, so it is safe to change it into a macro.
 */
/* NOTE: I (Mario - msousa@fe.up.pt) believe that the following function contains a bug when handling negative times.
 *       The equivalent macro has this bug fixed.
 *       e.g.;
 *          T#3.8s
 *       using the function, will result in a timespec of 3.8s !!!:
 *          tv_sec  =  4               <-----  1 *  3.8           is rounded up when converting a double to an int!
 *          tv_nsec = -200 000 000     <-----  1 * (3.8 - 4)*1e9
 *
 *         -T#3.8s
 *       using the function, will result in a timespec of -11.8s !!!:
 *          tv_sec  = -4                 <-----  -1 *  3.8 is rounded down when converting a double to an int!
 *          tv_nsec = -7 800 000 000     <-----  -1 * (3.8 - -4)*1e9
 */
/* NOTE: Due to the fact that the C compiler may round a tv_sec number away from zero,
 *       the following macro may result in a timespec that is not normalized, i.e. with a tv_sec > 0, and a tv_nsec < 0 !!!!
 *       This is due to the rounding that C compiler applies when converting a (long double) to a (long int).
 *       To produce normalized timespec's we need to use floor(), but we cannot call any library functions since we want this macro to be
 *       useable as a variable initializer.
 *       VAR x : TIME = T#3.5h; END_VAR --->  IEC_TIME x = __time_to_timespec(1, 0, 0, 0, 3.5, 0);
 */
/*
static inline IEC_TIMESPEC __time_to_timespec(int sign, double mseconds, double seconds, double minutes, double hours, double days) {
  IEC_TIMESPEC ts;

  // sign is 1 for positive values, -1 for negative time...
  long double total_sec = ((days*24 + hours)*60 + minutes)*60 + seconds + mseconds/1e3;
  if (sign >= 0) sign = 1; else sign = -1;
  ts.tv_sec = sign * (long int)total_sec;
  ts.tv_nsec = sign * (long int)((total_sec - ts.tv_sec)*1e9);

  return ts;
}
*/
/* NOTE: Unfortunately older versions of ANSI C (e.g. C99) do not allow explicit identification of elements in initializers
 *         e.g.  {tv_sec = 1, tv_nsec = 300}
 *       They are therefore commented out. This however means that any change to the definition of IEC_TIMESPEC may require this
 *       macro to be updated too!
 */
#define __time_to_timespec(sign, mseconds, seconds, minutes, hours, days) \
          ((IEC_TIMESPEC){\
              /*tv_sec  =*/ ((long int)   (((sign>=0)?1:-1)*((((long double)days*24 + (long double)hours)*60 + (long double)minutes)*60 + (long double)seconds + (long double)mseconds/1e3))), \
              /*tv_nsec =*/ ((long int)(( \
                            ((long double)(((sign>=0)?1:-1)*((((long double)days*24 + (long double)hours)*60 + (long double)minutes)*60 + (long double)seconds + (long double)mseconds/1e3))) - \
                            ((long int)   (((sign>=0)?1:-1)*((((long double)days*24 + (long double)hours)*60 + (long double)minutes)*60 + (long double)seconds + (long double)mseconds/1e3)))   \
                            )*1e9))\
        })




/* NOTE: The following function was turned into a macro, so it could be used to initialize the initial value of TOD (TIME_OF_DAY) variables */
/* NOTE: many (but not all) of the same comments made regarding __time_to_timespec() are also valid here, so go and read those comments too!*/
/*
static inline IEC_TIMESPEC __tod_to_timespec(double seconds, double minutes, double hours) {
  IEC_TIMESPEC ts;

  long double total_sec = (hours*60 + minutes)*60 + seconds;
  ts.tv_sec = (long int)total_sec;
  ts.tv_nsec = (long int)((total_sec - ts.tv_sec)*1e9);

  return ts;
}
*/
#define __tod_to_timespec(seconds, minutes, hours) \
          ((IEC_TIMESPEC){\
              /*tv_sec  =*/ ((long int)   ((((long double)hours)*60 + (long double)minutes)*60 + (long double)seconds)), \
              /*tv_nsec =*/ ((long int)(( \
                            ((long double)((((long double)hours)*60 + (long double)minutes)*60 + (long double)seconds)) - \
                            ((long int)   ((((long double)hours)*60 + (long double)minutes)*60 + (long double)seconds))   \
                            )*1e9))\
        })


#define EPOCH_YEAR 1970
#define SECONDS_PER_MINUTE 60
#define SECONDS_PER_HOUR (60 * SECONDS_PER_MINUTE)
#define SECONDS_PER_DAY (24 * SECONDS_PER_HOUR)
#define __isleap(year) \
  ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
static const unsigned short int __mon_yday[2][13] =
        {
                /* Normal years.  */
                {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
                /* Leap years.  */
                {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
        };

struct tm {
    int tm_sec;            /* Seconds.	[0-60] (1 leap second) */
    int tm_min;            /* Minutes.	[0-59] */
    int tm_hour;            /* Hours.	[0-23] */
    int tm_day;            /* Day.		[1-31] */
    int tm_mon;            /* Month.	[0-11] */
    int tm_year;            /* Year	*/
};

static inline struct tm convert_seconds_to_date_and_time(long int seconds) {
    struct tm dt;
    long int days, rem;
    days = seconds / SECONDS_PER_DAY;
    rem = seconds % SECONDS_PER_DAY;
    if (rem < 0) {
        rem += SECONDS_PER_DAY;
        days--;
    }

    // time of day
    dt.tm_hour = rem / SECONDS_PER_HOUR;
    rem %= SECONDS_PER_HOUR;
    dt.tm_min = rem / 60;
    dt.tm_sec = rem % 60;

    // date
    dt.tm_year = EPOCH_YEAR;
    while (days >= (rem = __isleap(dt.tm_year) ? 366 : 365)) {
        dt.tm_year++;
        days -= rem;
    }
    while (days < 0) {
        dt.tm_year--;
        days += __isleap(dt.tm_year) ? 366 : 365;
    }
    dt.tm_mon = 1;
    while (days >= __mon_yday[__isleap(dt.tm_year)][dt.tm_mon]) {
        dt.tm_mon += 1;
    }
    dt.tm_day = days - __mon_yday[__isleap(dt.tm_year)][dt.tm_mon - 1] + 1;

    return dt;
}

static inline IEC_TIMESPEC __date_to_timespec(int day, int month, int year) {
    IEC_TIMESPEC ts;
    int a4, b4, a100, b100, a400, b400;
    int yday;
    int intervening_leap_days;

    if (month < 1 || month > 12)
        __iec_error();

    yday = __mon_yday[__isleap(year)][month - 1] + day;

    if (yday > __mon_yday[__isleap(year)][month])
        __iec_error();

    a4 = (year >> 2) - !(year & 3);
    b4 = (EPOCH_YEAR >> 2) - !(EPOCH_YEAR & 3);
    a100 = a4 / 25 - (a4 % 25 < 0);
    b100 = b4 / 25 - (b4 % 25 < 0);
    a400 = a100 >> 2;
    b400 = b100 >> 2;
    intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);

    ts.tv_sec = ((year - EPOCH_YEAR) * 365 + intervening_leap_days + yday - 1) * 24 * 60 * 60;
    ts.tv_nsec = 0;

    return ts;
}

static inline IEC_TIMESPEC
__dt_to_timespec(double seconds, double minutes, double hours, int day, int month, int year) {
    IEC_TIMESPEC ts_date = __date_to_timespec(day, month, year);
    IEC_TIMESPEC ts = __tod_to_timespec(seconds, minutes, hours);

    ts.tv_sec += ts_date.tv_sec;

    return ts;
}

/*******************/
/* Time operations */
/*******************/

#define __time_cmp(t1, t2) (t2.tv_sec == t1.tv_sec ? t1.tv_nsec - t2.tv_nsec : t1.tv_sec - t2.tv_sec)

static inline TIME __time_add(TIME IN1, TIME IN2) {
    TIME res = {IN1.tv_sec + IN2.tv_sec,
                IN1.tv_nsec + IN2.tv_nsec};
    __normalize_timespec(&res);
    return res;
}

static inline TIME __time_sub(TIME IN1, TIME IN2) {
    TIME res = {IN1.tv_sec - IN2.tv_sec,
                IN1.tv_nsec - IN2.tv_nsec};
    __normalize_timespec(&res);
    return res;
}

static inline TIME __time_mul(TIME IN1, LREAL IN2);

static inline TIME __time_div(TIME IN1, LREAL IN2);


/***************/
/* Convertions */
/***************/
/*****************/
/*  REAL_TO_INT  */
/*****************/
LINT __real_round(LREAL IN);

static inline LINT __preal_to_sint(LREAL IN) {
    return IN >= 0 ? __real_round(IN + 0.5) : __real_round(IN - 0.5);
}

static inline LINT __preal_to_uint(LREAL IN) {
    return IN >= 0 ? __real_round(IN + 0.5) : 0;
}

static inline LINT __real_to_sint(LREAL IN) { return (LINT) __preal_to_sint(IN); }

static inline LWORD __real_to_bit(LREAL IN) { return (LWORD) __preal_to_uint(IN); }

static inline ULINT __real_to_uint(LREAL IN) { return (ULINT) __preal_to_uint(IN); }

/***************/
/*  TO_STRING  */
/***************/
static inline STRING __bool_to_string(BOOL IN) {
    if (IN) return (STRING) {4, "TRUE"};
    return (STRING) {5, "FALSE"};
}

static inline STRING __bit_to_string(LWORD IN);

static inline STRING __real_to_string(LREAL IN);

static inline STRING __sint_to_string(LINT IN);

STRING __uint_to_string(ULINT IN);
/***************/
/* FROM_STRING */
/***************/
static inline BOOL __string_to_bool(STRING IN);

static inline LINT __pstring_to_sint(STRING *IN) {
    LINT res = 0;
    __strlen_t l;
    unsigned int shift = 0;

    if (IN->body[0] == '2' && IN->body[1] == '#') {
        /* 2#0101_1010_1011_1111 */
        for (l = IN->len - 1; l >= 2 && shift < 64; l--) {
            char c = IN->body[l];
            if (c >= '0' && c <= '1') {
                res |= (c - '0') << shift;
                shift += 1;
            }
        }
    } else if (IN->body[0] == '8' && IN->body[1] == '#') {
        /* 8#1234_5665_4321 */
        for (l = IN->len - 1; l >= 2 && shift < 64; l--) {
            char c = IN->body[l];
            if (c >= '0' && c <= '7') {
                res |= (c - '0') << shift;
                shift += 3;
            }
        }
    } else if (IN->body[0] == '1' && IN->body[1] == '6' && IN->body[2] == '#') {
        /* 16#1234_5678_9abc_DEFG */
        for (l = IN->len - 1; l >= 3 && shift < 64; l--) {
            char c = IN->body[l];
            if (c >= '0' && c <= '9') {
                res |= (LWORD) (c - '0') << shift;
                shift += 4;
            } else if (c >= 'a' && c <= 'f') {
                res |= (LWORD) (c - 'a' + 10) << shift;
                shift += 4;
            } else if (c >= 'A' && c <= 'F') {
                res |= (LWORD) (c - 'A' + 10) << shift;
                shift += 4;
            }
        }
    } else {
        /* -123456789 */
        LINT fac = IN->body[0] == '-' ? -1 : 1;
        for (l = IN->len - 1; l >= 0 && shift < 20; l--) {
            char c = IN->body[l];
            if (c >= '0' && c <= '9') {
                res += (c - '0') * fac;
                fac *= 10;
                shift += 1;
            } else if (c >= '.') { /* reset value */
                res = 0;
                fac = IN->body[0] == '-' ? -1 : 1;
                shift = 0;
            }
        }
    }
    return res;
}

static inline LINT __string_to_sint(STRING IN) { return (LINT) __pstring_to_sint(&IN); }

static inline LWORD __string_to_bit(STRING IN) { return (LWORD) __pstring_to_sint(&IN); }

static inline ULINT __string_to_uint(STRING IN) { return (ULINT) __pstring_to_sint(&IN); }

static inline LREAL __string_to_real(STRING IN);
/*   TO_TIME   */
/***************/
static inline TIME __int_to_time(LINT IN) { return (TIME) {IN, 0}; }

TIME __real_to_time(LREAL IN);

static inline TIME __string_to_time(STRING IN);
/***************/
/*  FROM_TIME  */
/***************/
static inline LREAL __time_to_real(TIME IN) {
    return (LREAL) IN.tv_sec + ((LREAL) IN.tv_nsec / 1000000000);
}

static inline LINT __time_to_int(TIME IN) { return IN.tv_sec; }

static inline STRING __time_to_string(TIME IN);

static inline STRING __date_to_string(DATE IN);

static inline STRING __tod_to_string(TOD IN);

static inline STRING __dt_to_string(DT IN);
/**********************************************/
/*  [ANY_DATE | TIME] _TO_ [ANY_DATE | TIME]  */
/**********************************************/

static inline TOD __date_and_time_to_time_of_day(DT IN);

static inline DATE __date_and_time_to_date(DT IN);

/*****************/
/*  FROM/TO BCD  */
/*****************/

static inline BOOL __test_bcd(LWORD IN) {
    while (IN) {
        if ((IN & 0xf) > 9) return 1;
        IN >>= 4;
    }
    return 0;
}

static inline ULINT __bcd_to_uint(LWORD IN) {
    ULINT res = IN & 0xf;
    ULINT factor = 10ULL;

    while (IN >>= 4) {
        res += (IN & 0xf) * factor;
        factor *= 10;
    }
    return res;
}

static inline LWORD __uint_to_bcd(ULINT IN) {
    LWORD res = IN % 10;
    USINT shift = 4;

    while (IN /= 10) {
        res |= (IN % 10) << shift;
        shift += 4;
    }
    return res;
}


/************/
/*  MOVE_*  */
/************/

/* some helpful __move_[ANY] functions, used in the *_TO_** and MOVE  standard functions */
/* e.g. __move_BOOL, __move_BYTE, __move_REAL, __move_TIME, ... */
#define __move_(TYPENAME)\
static inline TYPENAME __move_##TYPENAME(TYPENAME op1) {return op1;}

__ANY(__move_)


#include "iec_std_functions.h"
#include "extra_pou.h"

#ifdef  DISABLE_EN_ENO_PARAMETERS
#include "iec_std_FB_no_ENENO.h"
#else

#include "iec_std_FB.h"

#endif

#endif /* _IEC_STD_LIB_H */
